package swag

import (
	"go/ast"
	"go/token"
	"regexp"
	"strings"

	"github.com/go-openapi/spec"
)

// Schema parsed schema.
type Schema struct {
	*spec.Schema        //
	PkgPath      string // package import path used to rename Name of a definition int case of conflict
	Name         string // Name in definitions
}

// TypeSpecDef the whole information of a typeSpec.
type TypeSpecDef struct {
	// ast file where TypeSpec is
	File *ast.File

	// the TypeSpec of this type definition
	TypeSpec *ast.TypeSpec

	Enums []EnumValue

	// path of package starting from under ${GOPATH}/src or from module path in go.mod
	PkgPath    string
	ParentSpec ast.Decl

	SchemaName string

	NotUnique bool
}

// Name the name of the typeSpec.
func (t *TypeSpecDef) Name() string {
	if t.TypeSpec != nil && t.TypeSpec.Name != nil {
		return t.TypeSpec.Name.Name
	}

	return ""
}

// TypeName the type name of the typeSpec.
func (t *TypeSpecDef) TypeName() string {
	if ignoreNameOverride(t.TypeSpec.Name.Name) {
		return t.TypeSpec.Name.Name[1:]
	}

	var names []string
	if t.NotUnique {
		pkgPath := strings.Map(func(r rune) rune {
			if r == '\\' || r == '/' || r == '.' {
				return '_'
			}
			return r
		}, t.PkgPath)
		names = append(names, pkgPath)
	} else if t.File != nil {
		names = append(names, t.File.Name.Name)
	}
	if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil {
		names = append(names, parentFun.Name.Name)
	}
	names = append(names, t.TypeSpec.Name.Name)
	return fullTypeName(names...)
}

// FullPath return the full path of the typeSpec.
func (t *TypeSpecDef) FullPath() string {
	return t.PkgPath + "." + t.Name()
}

const regexCaseInsensitive = "(?i)"

var reTypeName = regexp.MustCompile(regexCaseInsensitive + `^@name\s+(\S+)`)

func (t *TypeSpecDef) Alias() string {
	if t.TypeSpec.Comment == nil {
		return ""
	}

	// get alias from comment '// @name '
	for _, comment := range t.TypeSpec.Comment.List {
		trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
		texts := reTypeName.FindStringSubmatch(trimmedComment)
		if len(texts) > 1 {
			return texts[1]
		}
	}

	return ""
}

func (t *TypeSpecDef) SetSchemaName() {
	if alias := t.Alias(); alias != "" {
		t.SchemaName = alias
		return
	}

	t.SchemaName = t.TypeName()
}

// AstFileInfo information of an ast.File.
type AstFileInfo struct {
	//FileSet the FileSet object which is used to parse this go source file
	FileSet *token.FileSet

	// File ast.File
	File *ast.File

	// Path the path of the ast.File
	Path string

	// PackagePath package import path of the ast.File
	PackagePath string

	// ParseFlag determine what to parse
	ParseFlag ParseFlag
}
